Practice questions

Answer the following questions:

  1. How are row-standardized and binary spatial weights interpreted?
  2. What is the reason for using a Bonferroni correction for multiple tests?
  3. What types of spatial patterns can the local version of Moran’s I detect?
  4. What types of spatial patterns can the \(G_i(d)\) statistic detect?
  5. What is the utility of detecting hot and cold spatial spots?

Learning objectives

In this activity, you will:

  1. Calculate Moran’s I coefficient of autocorrelation for area data.
  2. Create Moran’s scatterplots.
  3. Examine the results of the tests/scatterplots for further insights.
  4. Think about ways to decide whether a landscape is random when working with area data.

Suggested reading

O’Sullivan D and Unwin D (2010) Geographic Information Analysis, 2nd Edition, Chapter 7. John Wiley & Sons: New Jersey.

Preliminaries

For this activity you will need the following:

It is good practice to clear the working space to make sure that you do not have extraneous items there when you begin your work. The command in R to clear the workspace is rm (for “remove”), followed by a list of items to be removed. To clear the workspace from all objects, do the following:

rm(list = ls())

Note that ls() lists all objects currently on the worspace.

Load the libraries you will use in this activity. In addition to tidyverse, you will need spdep, a package designed for the analysis of spatial data (you can learn about spdep here and here):

library(tidyverse)
-- Attaching packages --------------------------------- tidyverse 1.2.1 --
v ggplot2 2.2.1     v purrr   0.2.4
v tibble  1.4.2     v dplyr   0.7.4
v tidyr   0.8.0     v stringr 1.2.0
v readr   1.1.1     v forcats 0.2.0
-- Conflicts ------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(rgdal)
Loading required package: sp
rgdal: version: 1.2-16, (SVN revision 701)
 Geospatial Data Abstraction Library extensions to R successfully loaded
 Loaded GDAL runtime: GDAL 2.2.0, released 2017/04/28
 Path to GDAL shared files: C:/Users/Antonio/Documents/R/win-library/3.4/rgdal/gdal
 GDAL binary built with GEOS: TRUE 
 Loaded PROJ.4 runtime: Rel. 4.9.3, 15 August 2016, [PJ_VERSION: 493]
 Path to PROJ.4 shared files: C:/Users/Antonio/Documents/R/win-library/3.4/rgdal/proj
 Linking to sp version: 1.2-7 
library(broom)
library(spdep)
Loading required package: Matrix

Attaching package: <U+393C><U+3E31>Matrix<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:tidyr<U+393C><U+3E32>:

    expand

Loading required package: spData

Begin by loading the data that you will use in this activity:

Hamilton_CT <- readOGR(".", layer = "Hamilton CMA CT", integer64 = "allow.loss")
OGR data source with driver: ESRI Shapefile 
Source: ".", layer: "Hamilton CMA CT"
with 188 features
It has 255 fields
Integer64 fields read as signed 32-bit integers:  ID POPULATION PRIVATE_DW OCCUPIED_D ALL_AGES AGE_4 AGE_5_TO_9 AGE_10_TO_ AGE_15_TO_ AGE_15 AGE_16 AGE_17 AGE_18 AGE_19 AGE_20_TO_ AGE_25_TO_ AGE_30_TO_ AGE_35_TO_ AGE_40_TO_ AGE_45_TO_ AGE_50_TO_ AGE_55_TO_ AGE_60_TO_ AGE_65_TO_ AGE_70_TO_ AGE_75_TO_ AGE_80_TO_ AGE_85 MEDIAN_AGE MALE_ALL_A MALE_4 MALE_5_TO_ MALE_10_TO MALE_15_TO MALE_15 MALE_16 MALE_17 MALE_18 MALE_19 MALE_20_TO MALE_25_TO MALE_30_TO MALE_35_TO MALE_40_TO MALE_45_TO MALE_50_TO MALE_55_TO MALE_60_TO MALE_65_TO MALE_70_TO MALE_75_TO MALE_80_TO MALE_85 MALE_MEDIA FEMALE_ALL FEMALE_4 FEMALE_5_T FEMALE_10_ FEMALE_15_ FEMALE_15 FEMALE_16 FEMALE_17 FEMALE_18 FEMALE_19 FEMALE_20_ FEMALE_25_ FEMALE_30_ FEMALE_35_ FEMALE_40_ FEMALE_45_ FEMALE_50_ FEMALE_55_ FEMALE_60_ FEMALE_65_ FEMALE_70_ FEMALE_75_ FEMALE_80_ FEMALE_85 FEMALE_MED MARRIED_AG MARRIED_OR MARRIED COMMON_LAW UNMARRIED SINGLE SEPARATED DIVORCED WIDOWED MARRIED_A1 MARRIED_O1 MARRIED_M COMMON_LA1 UNMARRIED_ SINGLE_M SEPARATED_ DIVORCED_M WIDOWED_M MARRIED_A2 MARRIED_O2 MARRIED_F COMMON_LA2 UNMARRIED1 SINGLE_F SEPARATED1 DIVORCED_F WIDOWED_F FAMILIES_I FAMILY_SIZ FAMILY_SI1 FAMILY_SI2 FAMILY_SI3 COUPLE_FAM COUPLE_MAR COUPLE_MA1 COUPLE_MA2 COUPLE_MA3 COUPLE_MA4 COUPLE_MA5 COUPLE_COM COUPLE_CO1 COUPLE_CO2 COUPLE_CO3 COUPLE_CO4 COUPLE_CO5 SINGLE_PAR SINGLE_PA1 SINGLE_PA2 SINGLE_PA3 SINGLE_PA4 SINGLE_PA5 SINGLE_PA6 SINGLE_PA7 SINGLE_PA8 CHILDREN_F CHILDREN_1 CHILDREN_2 CHILDREN_3 CHILDREN_4 CHILDREN_5 POPULATIO1 POPULATIO2 POPULATIO3 POPULATIO4 POPULATIO5 POPULATIO6 POPULATIO7 POPULATIO8 POPULATIO9 POPULATI10 POPULATI11 POPULATI12 PRIVATE_HO PRIVATE_HH PRIVATE_H1 PRIVATE_H2 PRIVATE_H3 PRIVATE_H4 PRIVATE_H5 PRIVATE_H6 PRIVATE_H7 PRIVATE_H8 PRIVATE_H9 PRIVATE_10 PRIVATE_11 PRIVATE_12 PRIVATE_13 PRIVATE_14 PRIVATE_15 OCC_PRIVAT OCC_PRIVA1 OCC_PRIVA2 OCC_PRIVA3 OCC_PRIVA4 OCC_PRIVA5 OCC_PRIVA6 OCC_PRIVA7 OCC_PRIVA8 OCC_PRIVA9 PRIVATE_16 PRIVATE_17 PRIVATE_18 PRIVATE_19 PRIVATE_20 PRIVATE_21 PRIVATE_22 PRIVATE_23 NATIVE_LAN NATIVE_LA1 NATIVE_LA2 NATIVE_LA3 NATIVE_LA4 NATIVE_LA5 NATIVE_LA6 NATIVE_LA7 NATIVE_LA8 NATIVE_LA9 NATIVE_L10 NATIVE_L11 NATIVE_L12 NATIVE_L13 NATIVE_L14 NATIVE_L15 NATIVE_L16 NATIVE_L17 NATIVE_L18 NATIVE_L19 NATIVE_L20 NATIVE_L21 NATIVE_L22 NATIVE_L23 NATIVE_L24 NATIVE_L25 NATIVE_L26 NATIVE_L27 NATIVE_L28 NATIVE_L29 NATIVE_L30 NATIVE_L31 NATIVE_L32 NATIVE_L33 NATIVE_L34 NATIVE_L35 NATIVE_L36 NATIVE_L37 NATIVE_L38 NATIVE_L39 NATIVE_L40 NATIVE_L41 NATIVE_L42 NATIVE_L43 NATIVE_L44 NATIVE_L45 NATIVE_L46 NATIVE_L47 NATIVE_L48 NATIVE_L49 NATIVE_L50 NATIVE_L51 NATIVE_L52 NATIVE_L53 NATIVE_L54 NATIVE_L55 NATIVE_L56 NATIVE_L57 NATIVE_L58 NATIVE_L59 

You can obtain new (calculated) variables as follows. For instance, to obtain the proportion of residents who are between 20 and 34 years old, and between 35 and 49:

Hamilton_CT@data <- mutate(Hamilton_CT@data, Prop20to34 = (AGE_20_TO_ + AGE_25_TO_ + AGE_30_TO_)/POPULATION, Prop35to49 = (AGE_35_TO_ + AGE_40_TO_ + AGE_45_TO_)/POPULATION)

Example: Proportion of residents who are between 20 and 34 years old, and between 35 and 49:

Hamilton_CT@data <- dplyr::transmute(Hamilton_CT@data, AREA = AREA, TRACT = TRACT,
                                     POPULATION = POPULATION,
                                     POP20to34 = (AGE_20_TO_ + AGE_25_TO_ + AGE_30_TO_),
                                     Prop20to34 = POP20to34/POPULATION, 
                                     POP35to49 = (AGE_35_TO_ + AGE_40_TO_ + AGE_45_TO_),
                                     Prop35to49 = POP35to49/POPULATION, 
                                     POP50to64 = (AGE_50_TO_ + AGE_55_TO_ + AGE_60_TO_),
                                     Prop50to64 = POP50to64/POPULATION,
                                     POP65Plus = (AGE_65_TO_ + AGE_70_TO_ + AGE_75_TO_ + AGE_80_TO_ + AGE_85),
                                     Prop65Plus = POP65Plus/POPULATION)

This is a SpatialPolygonDataFrame. Convert to a dataframe (“tidy” it) for plotting using ggplot2:

Hamilton_CT.t <- tidy(Hamilton_CT, region = "TRACT")
Hamilton_CT.t <- rename(Hamilton_CT.t, TRACT = id)

Rejoin the data:

Hamilton_CT.t <- left_join(Hamilton_CT.t, Hamilton_CT@data, by = "TRACT")
Column `TRACT` joining character vector and factor, coercing into character vector

This is the function to create local Moran maps:

localmoran.map <- function(spat_pol = spat_pol, listw = listw, VAR = VAR, ID = ID){
  require(tidyverse)
  require(broom)
  require(spdep)
  require(plotly)
  
  spat_pol@data <- data.frame(ID = ID, VAR = VAR)
  spat_pol.t <- broom::tidy(spat_pol, region = "ID")
  spat_pol.t <- dplyr::rename(spat_pol.t, ID = id)
  spat_pol.t <- dplyr::left_join(spat_pol.t, spat_pol@data, by = "ID")
  
  df_msc <- transmute(spat_pol@data, 
                      ID = ID,
                      Z = (VAR-mean(VAR)) / var(VAR), 
                      SMA = lag.listw(listw, Z),
                      Type = factor(ifelse(Z < 0 & SMA < 0, "LL", 
                                           ifelse(Z > 0 & SMA > 0, "HH", "HL/LH"))))
  
  local_I <- localmoran(spat_pol$VAR, listw)
  
  spat_pol.t <- left_join(spat_pol.t, 
                             data.frame(ID = spat_pol$ID, local_I))
  spat_pol.t <- rename(spat_pol.t, p.val = Pr.z...0.)
  spat_pol.t <- left_join(spat_pol.t, 
                             df_msc)
  
  map <- ggplot(data = spat_pol.t, 
                aes(x = long, y = lat, group = group, 
                    p.val = p.val, VAR = VAR)) +
    geom_polygon(aes(fill = Type, color = p.val < 0.05)) +
    scale_fill_brewer(palette = "RdBu") +
    scale_color_manual(values = c(NA, "Black") ) +
    labs(color = "Prob < 0.05") +
    coord_equal() +
    theme(legend.title = element_blank())
  ggplotly(map, tooltip = c("p.val", "VAR"))
}

This is function is used to create \(G_i^*\) maps:

gistar.map <- function(spat_pol = spat_pol, listw = listw, VAR = VAR, ID = ID){
  require(tidyverse)
  require(broom)
  require(spdep)
  require(plotly)
  
  spat_pol@data <- data.frame(ID = ID, VAR = VAR)
  spat_pol.t <- broom::tidy(spat_pol, region = "ID")
  spat_pol.t <- dplyr::rename(spat_pol.t, ID = id)
  spat_pol.t <- dplyr::left_join(spat_pol.t, spat_pol@data, by = "ID")
  
  df.lg <- localG(VAR, listw)
  df.lg <- as.numeric(df.lg)
  df.lg <- data.frame(Gstar = df.lg, p.val = 2 * pnorm(abs(df.lg), lower.tail = FALSE))
  
  df.lg <- mutate(df.lg, 
              Type = factor(ifelse(Gstar < 0 & p.val <= 0.05, "Low Concentration",
                                   ifelse(Gstar > 0 & p.val <= 0.05, "High Concentration", "Not Signicant"))))
  spat_pol.t <- left_join(spat_pol.t,
                             data.frame(ID = spat_pol$ID, df.lg))
  map <- ggplot(data = spat_pol.t, 
                aes(x = long, y = lat, group = group, 
                    p.val = p.val, VAR = VAR)) +
    geom_polygon(aes(fill = Type, color = p.val < 0.05)) +
    scale_fill_brewer(palette = "RdBu") +
    scale_color_manual(values = c(NA, "Black") ) +
    labs(color = "Prob < 0.05") +
    coord_equal() +
    theme(legend.title = element_blank())
  ggplotly(map, tooltip = c("p.val", "VAR"))
}

Create spatial weights.

  1. By contiguity:
Hamilton_CT.w <- nb2listw(poly2nb(pl = Hamilton_CT))
  1. Binary, by distance (3 km threshold) including self.
Hamilton_CT.3knb <- Hamilton_CT %>% coordinates() %>% dnearneigh(d1 = 0, d2 = 3, longlat = TRUE)
Hamilton_CT.3kw <- nb2listw(include.self(Hamilton_CT.3knb), style = "B")

Activity

  1. Create local Moran maps for the population and proportion of population in the age group 20-34. What is the difference between using population (absolute) and proportion of population (rate)? Is there a reason to prefer either variable in analysis? Discuss.
localmoran.map(Hamilton_CT, Hamilton_CT.w, Hamilton_CT$POP20to34, Hamilton_CT$TRACT)
Loading required package: plotly

Attaching package: <U+393C><U+3E31>plotly<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:ggplot2<U+393C><U+3E32>:

    last_plot

The following object is masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:

    filter

The following object is masked from <U+393C><U+3E31>package:graphics<U+393C><U+3E32>:

    layout

Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorWe recommend that you use the dev version of ggplot2 with `ggplotly()`
Install it with: `devtools::install_github('hadley/ggplot2')`
localmoran.map(Hamilton_CT, Hamilton_CT.w, Hamilton_CT$Prop20to34, Hamilton_CT$TRACT)
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorWe recommend that you use the dev version of ggplot2 with `ggplotly()`
Install it with: `devtools::install_github('hadley/ggplot2')`
  1. Use the \(G_i^*\) statitic to analyze the population and proportion of population in the age group 20-34. What is the difference between using population (absolute) and proportion of population (rate)? Is there a reason to prefer either variable in analysis? Discuss.
gistar.map(Hamilton_CT, Hamilton_CT.3kw, Hamilton_CT$POP20to34, Hamilton_CT$TRACT)
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorWe recommend that you use the dev version of ggplot2 with `ggplotly()`
Install it with: `devtools::install_github('hadley/ggplot2')`
gistar.map(Hamilton_CT, Hamilton_CT.3kw, Hamilton_CT$Prop20to34, Hamilton_CT$TRACT)
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorWe recommend that you use the dev version of ggplot2 with `ggplotly()`
Install it with: `devtools::install_github('hadley/ggplot2')`
  1. Now create local Moran maps for the population and population density in the age group 20-34. What is the difference between using population (absolute) and population density (rate)?
localmoran.map(Hamilton_CT, Hamilton_CT.w, Hamilton_CT$POP20to34, Hamilton_CT$TRACT)
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorWe recommend that you use the dev version of ggplot2 with `ggplotly()`
Install it with: `devtools::install_github('hadley/ggplot2')`
localmoran.map(Hamilton_CT, Hamilton_CT.w, Hamilton_CT$POP20to34/Hamilton_CT$AREA, Hamilton_CT$TRACT)
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorJoining, by = "ID"
Column `ID` joining character vector and factor, coercing into character vectorWe recommend that you use the dev version of ggplot2 with `ggplotly()`
Install it with: `devtools::install_github('hadley/ggplot2')`
  1. More generally, what do you think should guide the decision of whether to analyze variables as absolute values or rates?
LS0tDQp0aXRsZTogIjEyIFNlc3Npb24gMTI6IEFyZWEgRGF0YSBJViINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiNQcmFjdGljZSBxdWVzdGlvbnMNCg0KQW5zd2VyIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb25zOg0KDQoxLiBIb3cgYXJlIHJvdy1zdGFuZGFyZGl6ZWQgYW5kIGJpbmFyeSBzcGF0aWFsIHdlaWdodHMgaW50ZXJwcmV0ZWQ/DQoyLiBXaGF0IGlzIHRoZSByZWFzb24gZm9yIHVzaW5nIGEgQm9uZmVycm9uaSBjb3JyZWN0aW9uIGZvciBtdWx0aXBsZSB0ZXN0cz8NCjMuIFdoYXQgdHlwZXMgb2Ygc3BhdGlhbCBwYXR0ZXJucyBjYW4gdGhlIGxvY2FsIHZlcnNpb24gb2YgTW9yYW4ncyBJIGRldGVjdD8NCjQuIFdoYXQgdHlwZXMgb2Ygc3BhdGlhbCBwYXR0ZXJucyBjYW4gdGhlICRHX2koZCkkIHN0YXRpc3RpYyBkZXRlY3Q/DQo1LiBXaGF0IGlzIHRoZSB1dGlsaXR5IG9mIGRldGVjdGluZyBob3QgYW5kIGNvbGQgc3BhdGlhbCBzcG90cz8NCg0KI0xlYXJuaW5nIG9iamVjdGl2ZXMNCg0KSW4gdGhpcyBhY3Rpdml0eSwgeW91IHdpbGw6DQoNCjEuIENhbGN1bGF0ZSBNb3JhbidzIEkgY29lZmZpY2llbnQgb2YgYXV0b2NvcnJlbGF0aW9uIGZvciBhcmVhIGRhdGEuDQoyLiBDcmVhdGUgTW9yYW4ncyBzY2F0dGVycGxvdHMuDQoyLiBFeGFtaW5lIHRoZSByZXN1bHRzIG9mIHRoZSB0ZXN0cy9zY2F0dGVycGxvdHMgZm9yIGZ1cnRoZXIgaW5zaWdodHMuDQozLiBUaGluayBhYm91dCB3YXlzIHRvIGRlY2lkZSB3aGV0aGVyIGEgbGFuZHNjYXBlIGlzIHJhbmRvbSB3aGVuIHdvcmtpbmcgd2l0aCBhcmVhIGRhdGEuDQoNCiNTdWdnZXN0ZWQgcmVhZGluZw0KDQpPJ1N1bGxpdmFuIEQgYW5kIFVud2luIEQgKDIwMTApIEdlb2dyYXBoaWMgSW5mb3JtYXRpb24gQW5hbHlzaXMsIDJuZCBFZGl0aW9uLCBDaGFwdGVyIDcuIEpvaG4gV2lsZXkgJiBTb25zOiBOZXcgSmVyc2V5LiANCg0KI1ByZWxpbWluYXJpZXMNCg0KRm9yIHRoaXMgYWN0aXZpdHkgeW91IHdpbGwgbmVlZCB0aGUgZm9sbG93aW5nOg0KDQoqIFRoaXMgUiBtYXJrZG93biBub3RlYm9vay4NCiogQSBzaGFwZSBmaWxlIGNhbGxlZCBgSGFtaWx0b24gQ01BIENUYC4NCg0KSXQgaXMgZ29vZCBwcmFjdGljZSB0byBjbGVhciB0aGUgd29ya2luZyBzcGFjZSB0byBtYWtlIHN1cmUgdGhhdCB5b3UgZG8gbm90IGhhdmUgZXh0cmFuZW91cyBpdGVtcyB0aGVyZSB3aGVuIHlvdSBiZWdpbiB5b3VyIHdvcmsuIFRoZSBjb21tYW5kIGluIFIgdG8gY2xlYXIgdGhlIHdvcmtzcGFjZSBpcyBgcm1gIChmb3IgInJlbW92ZSIpLCBmb2xsb3dlZCBieSBhIGxpc3Qgb2YgaXRlbXMgdG8gYmUgcmVtb3ZlZC4gVG8gY2xlYXIgdGhlIHdvcmtzcGFjZSBmcm9tIF9hbGxfIG9iamVjdHMsIGRvIHRoZSBmb2xsb3dpbmc6DQpgYGB7cn0NCnJtKGxpc3QgPSBscygpKQ0KYGBgDQoNCk5vdGUgdGhhdCBgbHMoKWAgbGlzdHMgYWxsIG9iamVjdHMgY3VycmVudGx5IG9uIHRoZSB3b3JzcGFjZS4NCg0KTG9hZCB0aGUgbGlicmFyaWVzIHlvdSB3aWxsIHVzZSBpbiB0aGlzIGFjdGl2aXR5LiBJbiBhZGRpdGlvbiB0byBgdGlkeXZlcnNlYCwgeW91IHdpbGwgbmVlZCBgc3BkZXBgLCBhIHBhY2thZ2UgZGVzaWduZWQgZm9yIHRoZSBhbmFseXNpcyBvZiBzcGF0aWFsIGRhdGEgKHlvdSBjYW4gbGVhcm4gYWJvdXQgYHNwZGVwYCBbaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NwZGVwL2luZGV4Lmh0bWwpIGFuZCBbaGVyZV0oaHR0cDovLzIwMTQub2dycy1jb21tdW5pdHkub3JnLzIwMTRfd29ya3Nob3BzL1JfU3BhdGlhbF9CaXZhbmQvb2dyc18xNDA2MTIucGRmKSk6DQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShyZ2RhbCkNCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KHNwZGVwKQ0KYGBgDQoNCkJlZ2luIGJ5IGxvYWRpbmcgdGhlIGRhdGEgdGhhdCB5b3Ugd2lsbCB1c2UgaW4gdGhpcyBhY3Rpdml0eToNCmBgYHtyfQ0KSGFtaWx0b25fQ1QgPC0gcmVhZE9HUigiLiIsIGxheWVyID0gIkhhbWlsdG9uIENNQSBDVCIsIGludGVnZXI2NCA9ICJhbGxvdy5sb3NzIikNCmBgYA0KDQpZb3UgY2FuIG9idGFpbiBuZXcgKGNhbGN1bGF0ZWQpIHZhcmlhYmxlcyBhcyBmb2xsb3dzLiBGb3IgaW5zdGFuY2UsIHRvIG9idGFpbiB0aGUgcHJvcG9ydGlvbiBvZiByZXNpZGVudHMgd2hvIGFyZSBiZXR3ZWVuIDIwIGFuZCAzNCB5ZWFycyBvbGQsIGFuZCBiZXR3ZWVuIDM1IGFuZCA0OToNCmBgYHtyfQ0KSGFtaWx0b25fQ1RAZGF0YSA8LSBtdXRhdGUoSGFtaWx0b25fQ1RAZGF0YSwgUHJvcDIwdG8zNCA9IChBR0VfMjBfVE9fICsgQUdFXzI1X1RPXyArIEFHRV8zMF9UT18pL1BPUFVMQVRJT04sIFByb3AzNXRvNDkgPSAoQUdFXzM1X1RPXyArIEFHRV80MF9UT18gKyBBR0VfNDVfVE9fKS9QT1BVTEFUSU9OKQ0KYGBgDQoNCkV4YW1wbGU6IFByb3BvcnRpb24gb2YgcmVzaWRlbnRzIHdobyBhcmUgYmV0d2VlbiAyMCBhbmQgMzQgeWVhcnMgb2xkLCBhbmQgYmV0d2VlbiAzNSBhbmQgNDk6DQpgYGB7cn0NCkhhbWlsdG9uX0NUQGRhdGEgPC0gZHBseXI6OnRyYW5zbXV0ZShIYW1pbHRvbl9DVEBkYXRhLCBBUkVBID0gQVJFQSwgVFJBQ1QgPSBUUkFDVCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQT1BVTEFUSU9OID0gUE9QVUxBVElPTiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQT1AyMHRvMzQgPSAoQUdFXzIwX1RPXyArIEFHRV8yNV9UT18gKyBBR0VfMzBfVE9fKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcm9wMjB0bzM0ID0gUE9QMjB0bzM0L1BPUFVMQVRJT04sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBPUDM1dG80OSA9IChBR0VfMzVfVE9fICsgQUdFXzQwX1RPXyArIEFHRV80NV9UT18pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByb3AzNXRvNDkgPSBQT1AzNXRvNDkvUE9QVUxBVElPTiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUE9QNTB0bzY0ID0gKEFHRV81MF9UT18gKyBBR0VfNTVfVE9fICsgQUdFXzYwX1RPXyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJvcDUwdG82NCA9IFBPUDUwdG82NC9QT1BVTEFUSU9OLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBPUDY1UGx1cyA9IChBR0VfNjVfVE9fICsgQUdFXzcwX1RPXyArIEFHRV83NV9UT18gKyBBR0VfODBfVE9fICsgQUdFXzg1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcm9wNjVQbHVzID0gUE9QNjVQbHVzL1BPUFVMQVRJT04pDQpgYGANCg0KVGhpcyBpcyBhIGBTcGF0aWFsUG9seWdvbkRhdGFGcmFtZWAuIENvbnZlcnQgdG8gYSBkYXRhZnJhbWUgKCJ0aWR5IiBpdCkgZm9yIHBsb3R0aW5nIHVzaW5nIGBnZ3Bsb3QyYDoNCmBgYHtyfQ0KSGFtaWx0b25fQ1QudCA8LSB0aWR5KEhhbWlsdG9uX0NULCByZWdpb24gPSAiVFJBQ1QiKQ0KSGFtaWx0b25fQ1QudCA8LSByZW5hbWUoSGFtaWx0b25fQ1QudCwgVFJBQ1QgPSBpZCkNCmBgYA0KDQpSZWpvaW4gdGhlIGRhdGE6DQpgYGB7cn0NCkhhbWlsdG9uX0NULnQgPC0gbGVmdF9qb2luKEhhbWlsdG9uX0NULnQsIEhhbWlsdG9uX0NUQGRhdGEsIGJ5ID0gIlRSQUNUIikNCmBgYA0KDQpUaGlzIGlzIHRoZSBmdW5jdGlvbiB0byBjcmVhdGUgbG9jYWwgTW9yYW4gbWFwczoNCmBgYHtyfQ0KbG9jYWxtb3Jhbi5tYXAgPC0gZnVuY3Rpb24oc3BhdF9wb2wgPSBzcGF0X3BvbCwgbGlzdHcgPSBsaXN0dywgVkFSID0gVkFSLCBJRCA9IElEKXsNCiAgcmVxdWlyZSh0aWR5dmVyc2UpDQogIHJlcXVpcmUoYnJvb20pDQogIHJlcXVpcmUoc3BkZXApDQogIHJlcXVpcmUocGxvdGx5KQ0KICANCiAgc3BhdF9wb2xAZGF0YSA8LSBkYXRhLmZyYW1lKElEID0gSUQsIFZBUiA9IFZBUikNCiAgc3BhdF9wb2wudCA8LSBicm9vbTo6dGlkeShzcGF0X3BvbCwgcmVnaW9uID0gIklEIikNCiAgc3BhdF9wb2wudCA8LSBkcGx5cjo6cmVuYW1lKHNwYXRfcG9sLnQsIElEID0gaWQpDQogIHNwYXRfcG9sLnQgPC0gZHBseXI6OmxlZnRfam9pbihzcGF0X3BvbC50LCBzcGF0X3BvbEBkYXRhLCBieSA9ICJJRCIpDQogIA0KICBkZl9tc2MgPC0gdHJhbnNtdXRlKHNwYXRfcG9sQGRhdGEsIA0KICAgICAgICAgICAgICAgICAgICAgIElEID0gSUQsDQogICAgICAgICAgICAgICAgICAgICAgWiA9IChWQVItbWVhbihWQVIpKSAvIHZhcihWQVIpLCANCiAgICAgICAgICAgICAgICAgICAgICBTTUEgPSBsYWcubGlzdHcobGlzdHcsIFopLA0KICAgICAgICAgICAgICAgICAgICAgIFR5cGUgPSBmYWN0b3IoaWZlbHNlKFogPCAwICYgU01BIDwgMCwgIkxMIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFogPiAwICYgU01BID4gMCwgIkhIIiwgIkhML0xIIikpKSkNCiAgDQogIGxvY2FsX0kgPC0gbG9jYWxtb3JhbihzcGF0X3BvbCRWQVIsIGxpc3R3KQ0KICANCiAgc3BhdF9wb2wudCA8LSBsZWZ0X2pvaW4oc3BhdF9wb2wudCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoSUQgPSBzcGF0X3BvbCRJRCwgbG9jYWxfSSkpDQogIHNwYXRfcG9sLnQgPC0gcmVuYW1lKHNwYXRfcG9sLnQsIHAudmFsID0gUHIuei4uLjAuKQ0KICBzcGF0X3BvbC50IDwtIGxlZnRfam9pbihzcGF0X3BvbC50LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfbXNjKQ0KICANCiAgbWFwIDwtIGdncGxvdChkYXRhID0gc3BhdF9wb2wudCwgDQogICAgICAgICAgICAgICAgYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCANCiAgICAgICAgICAgICAgICAgICAgcC52YWwgPSBwLnZhbCwgVkFSID0gVkFSKSkgKw0KICAgIGdlb21fcG9seWdvbihhZXMoZmlsbCA9IFR5cGUsIGNvbG9yID0gcC52YWwgPCAwLjA1KSkgKw0KICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUmRCdSIpICsNCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhOQSwgIkJsYWNrIikgKSArDQogICAgbGFicyhjb2xvciA9ICJQcm9iIDwgMC4wNSIpICsNCiAgICBjb29yZF9lcXVhbCgpICsNCiAgICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpDQogIGdncGxvdGx5KG1hcCwgdG9vbHRpcCA9IGMoInAudmFsIiwgIlZBUiIpKQ0KfQ0KYGBgDQoNClRoaXMgaXMgZnVuY3Rpb24gaXMgdXNlZCB0byBjcmVhdGUgJEdfaV4qJCBtYXBzOg0KYGBge3J9DQpnaXN0YXIubWFwIDwtIGZ1bmN0aW9uKHNwYXRfcG9sID0gc3BhdF9wb2wsIGxpc3R3ID0gbGlzdHcsIFZBUiA9IFZBUiwgSUQgPSBJRCl7DQogIHJlcXVpcmUodGlkeXZlcnNlKQ0KICByZXF1aXJlKGJyb29tKQ0KICByZXF1aXJlKHNwZGVwKQ0KICByZXF1aXJlKHBsb3RseSkNCiAgDQogIHNwYXRfcG9sQGRhdGEgPC0gZGF0YS5mcmFtZShJRCA9IElELCBWQVIgPSBWQVIpDQogIHNwYXRfcG9sLnQgPC0gYnJvb206OnRpZHkoc3BhdF9wb2wsIHJlZ2lvbiA9ICJJRCIpDQogIHNwYXRfcG9sLnQgPC0gZHBseXI6OnJlbmFtZShzcGF0X3BvbC50LCBJRCA9IGlkKQ0KICBzcGF0X3BvbC50IDwtIGRwbHlyOjpsZWZ0X2pvaW4oc3BhdF9wb2wudCwgc3BhdF9wb2xAZGF0YSwgYnkgPSAiSUQiKQ0KICANCiAgZGYubGcgPC0gbG9jYWxHKFZBUiwgbGlzdHcpDQogIGRmLmxnIDwtIGFzLm51bWVyaWMoZGYubGcpDQogIGRmLmxnIDwtIGRhdGEuZnJhbWUoR3N0YXIgPSBkZi5sZywgcC52YWwgPSAyICogcG5vcm0oYWJzKGRmLmxnKSwgbG93ZXIudGFpbCA9IEZBTFNFKSkNCiAgDQogIGRmLmxnIDwtIG11dGF0ZShkZi5sZywgDQogICAgICAgICAgICAgIFR5cGUgPSBmYWN0b3IoaWZlbHNlKEdzdGFyIDwgMCAmIHAudmFsIDw9IDAuMDUsICJMb3cgQ29uY2VudHJhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShHc3RhciA+IDAgJiBwLnZhbCA8PSAwLjA1LCAiSGlnaCBDb25jZW50cmF0aW9uIiwgIk5vdCBTaWduaWNhbnQiKSkpKQ0KDQogIHNwYXRfcG9sLnQgPC0gbGVmdF9qb2luKHNwYXRfcG9sLnQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoSUQgPSBzcGF0X3BvbCRJRCwgZGYubGcpKQ0KDQogIG1hcCA8LSBnZ3Bsb3QoZGF0YSA9IHNwYXRfcG9sLnQsIA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCwgDQogICAgICAgICAgICAgICAgICAgIHAudmFsID0gcC52YWwsIFZBUiA9IFZBUikpICsNCiAgICBnZW9tX3BvbHlnb24oYWVzKGZpbGwgPSBUeXBlLCBjb2xvciA9IHAudmFsIDwgMC4wNSkpICsNCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlJkQnUiKSArDQogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoTkEsICJCbGFjayIpICkgKw0KICAgIGxhYnMoY29sb3IgPSAiUHJvYiA8IDAuMDUiKSArDQogICAgY29vcmRfZXF1YWwoKSArDQogICAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQ0KICBnZ3Bsb3RseShtYXAsIHRvb2x0aXAgPSBjKCJwLnZhbCIsICJWQVIiKSkNCn0NCmBgYA0KDQpDcmVhdGUgc3BhdGlhbCB3ZWlnaHRzLg0KDQoxKSBCeSBjb250aWd1aXR5Og0KYGBge3J9DQpIYW1pbHRvbl9DVC53IDwtIG5iMmxpc3R3KHBvbHkybmIocGwgPSBIYW1pbHRvbl9DVCkpDQpgYGANCg0KMikgQmluYXJ5LCBieSBkaXN0YW5jZSAoMyBrbSB0aHJlc2hvbGQpIF9pbmNsdWRpbmcgc2VsZl8uDQpgYGB7cn0NCkhhbWlsdG9uX0NULjNrbmIgPC0gSGFtaWx0b25fQ1QgJT4lIGNvb3JkaW5hdGVzKCkgJT4lIGRuZWFybmVpZ2goZDEgPSAwLCBkMiA9IDMsIGxvbmdsYXQgPSBUUlVFKQ0KSGFtaWx0b25fQ1QuM2t3IDwtIG5iMmxpc3R3KGluY2x1ZGUuc2VsZihIYW1pbHRvbl9DVC4za25iKSwgc3R5bGUgPSAiQiIpDQpgYGANCg0KI0FjdGl2aXR5DQoNCjEuIENyZWF0ZSBsb2NhbCBNb3JhbiBtYXBzIGZvciB0aGUgcG9wdWxhdGlvbiBfYW5kXyBwcm9wb3J0aW9uIG9mIHBvcHVsYXRpb24gaW4gdGhlIGFnZSBncm91cCAyMC0zNC4gV2hhdCBpcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHVzaW5nIHBvcHVsYXRpb24gKGFic29sdXRlKSBhbmQgcHJvcG9ydGlvbiBvZiBwb3B1bGF0aW9uIChyYXRlKT8gSXMgdGhlcmUgYSByZWFzb24gdG8gcHJlZmVyIGVpdGhlciB2YXJpYWJsZSBpbiBhbmFseXNpcz8gRGlzY3Vzcy4NCg0KYGBge3J9DQpsb2NhbG1vcmFuLm1hcChIYW1pbHRvbl9DVCwgSGFtaWx0b25fQ1QudywgSGFtaWx0b25fQ1QkUE9QMjB0bzM0LCBIYW1pbHRvbl9DVCRUUkFDVCkNCmBgYA0KDQpgYGB7cn0NCmxvY2FsbW9yYW4ubWFwKEhhbWlsdG9uX0NULCBIYW1pbHRvbl9DVC53LCBIYW1pbHRvbl9DVCRQcm9wMjB0bzM0LCBIYW1pbHRvbl9DVCRUUkFDVCkNCmBgYA0KDQoyLiBVc2UgdGhlICRHX2leKiQgc3RhdGl0aWMgdG8gYW5hbHl6ZSB0aGUgcG9wdWxhdGlvbiBfYW5kXyBwcm9wb3J0aW9uIG9mIHBvcHVsYXRpb24gaW4gdGhlIGFnZSBncm91cCAyMC0zNC4gV2hhdCBpcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHVzaW5nIHBvcHVsYXRpb24gKGFic29sdXRlKSBhbmQgcHJvcG9ydGlvbiBvZiBwb3B1bGF0aW9uIChyYXRlKT8gSXMgdGhlcmUgYSByZWFzb24gdG8gcHJlZmVyIGVpdGhlciB2YXJpYWJsZSBpbiBhbmFseXNpcz8gRGlzY3Vzcy4NCg0KYGBge3J9DQpnaXN0YXIubWFwKEhhbWlsdG9uX0NULCBIYW1pbHRvbl9DVC4za3csIEhhbWlsdG9uX0NUJFBPUDIwdG8zNCwgSGFtaWx0b25fQ1QkVFJBQ1QpDQpgYGANCg0KYGBge3J9DQpnaXN0YXIubWFwKEhhbWlsdG9uX0NULCBIYW1pbHRvbl9DVC4za3csIEhhbWlsdG9uX0NUJFByb3AyMHRvMzQsIEhhbWlsdG9uX0NUJFRSQUNUKQ0KYGBgDQoNCjMuIE5vdyBjcmVhdGUgbG9jYWwgTW9yYW4gbWFwcyBmb3IgdGhlIHBvcHVsYXRpb24gX2FuZF8gcG9wdWxhdGlvbiBkZW5zaXR5IGluIHRoZSBhZ2UgZ3JvdXAgMjAtMzQuIFdoYXQgaXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB1c2luZyBwb3B1bGF0aW9uIChhYnNvbHV0ZSkgYW5kIHBvcHVsYXRpb24gZGVuc2l0eSAocmF0ZSk/DQoNCmBgYHtyfQ0KbG9jYWxtb3Jhbi5tYXAoSGFtaWx0b25fQ1QsIEhhbWlsdG9uX0NULncsIEhhbWlsdG9uX0NUJFBPUDIwdG8zNCwgSGFtaWx0b25fQ1QkVFJBQ1QpDQpgYGANCg0KYGBge3J9DQpsb2NhbG1vcmFuLm1hcChIYW1pbHRvbl9DVCwgSGFtaWx0b25fQ1QudywgSGFtaWx0b25fQ1QkUE9QMjB0bzM0L0hhbWlsdG9uX0NUJEFSRUEsIEhhbWlsdG9uX0NUJFRSQUNUKQ0KYGBgDQoNCjQuIE1vcmUgZ2VuZXJhbGx5LCB3aGF0IGRvIHlvdSB0aGluayBzaG91bGQgZ3VpZGUgdGhlIGRlY2lzaW9uIG9mIHdoZXRoZXIgdG8gYW5hbHl6ZSB2YXJpYWJsZXMgYXMgYWJzb2x1dGUgdmFsdWVzIG9yIHJhdGVzPw==